package pers.amos.eshop.thread;

import pers.amos.eshop.request.Request;
import pers.amos.eshop.request.RequestQueue;
import pers.amos.eshop.request.support.ProductInventoryDBUpdateRequest;
import pers.amos.eshop.request.support.ProductInventoryRefreshCacheRequest;

import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;

/**
 * @author amos wong
 * @create 2020-11-14 3:26 下午
 * <p>
 * 工作线程-请求处理线程
 */

public class RequestProcessorThread implements Callable<Boolean> {

    /**
     * 自己监控的内存队列
     */
    private ArrayBlockingQueue<Request> queue;

    public RequestProcessorThread(ArrayBlockingQueue<Request> queue) {
        this.queue = queue;
    }

    /**
     * 请求去重，压入内存队列的逻辑写在该方法中是为了避免多线程情况下的线程不安全问题（比如flag的并发读写在多线程情况下是不安全的）
     * Request request = queue.take(); 每次只会取出一个请求去处理 也就不会发生并发读写的问题
     *
     * @return
     * @throws Exception
     */
    @Override
    public Boolean call() throws Exception {
        try {
            while (true) {
                Request request = queue.take();
                boolean forceFresh = request.isForceRefresh();

                if (!forceFresh) {
                    // 1.先做请求的去重
                    RequestQueue requestQueue = RequestQueue.getInstance();
                    Map<Long, Boolean> flagMap = requestQueue.getFlagMap();
                    Long productId = request.getProductId();

                    // 2.优化：读请求的去重操作
                    Boolean flag = flagMap.get(productId);

                    // 如果当前请求是一个写请求，则将对应的标志位设置为true
                    if (request instanceof ProductInventoryDBUpdateRequest) {
                        flagMap.put(productId, true);
                    }

                    // 如果当前请求是一个读请求
                    if (request instanceof ProductInventoryRefreshCacheRequest) {

                        /**
                         * 思考一种情况：初始情况下商品库存信息会被放入redis中，如果一直没有进行任何的
                         * 读写操作，那么flag一直为null。等到redis内存满了 使用LRU策略删除了该缓存
                         * 如果不手动设置为false的话 将一直无法读取到该商品的库存信息
                         * 所以这里设置为false，让它hang一会 如果超时还没从redis中获取数据 则直接从数据库里查询
                         * 但是这样也有一个漏洞：读请求hang一会之后会直接从数据库读取数据 然后直接返回 并不会更新缓存
                         * 这样缓存中 还是一直都没有数据
                         * 解决方案：最简单的一种，就是在controller这一块，如果在数据库中查询到了，就刷新到缓存里面去，以后的读请求就又可以从缓存里面读了
                         */
                        if (flag == null) {
                            flagMap.put(productId, false);
                        }

                        // 如果flag不为空并且flag为true则表明在队列中已经有一个写请求了
                        // 此时将标识为设置为false 这样就表明内存队列中已经有一个读请求和一个写请求了
                        if (flag != null && flag) {
                            flagMap.put(productId, false);
                        }

                        // flag为false，说明内存队列中已经存在一个读请求和一个写请求了
                        // 这次读请求不用压入内存队列 直接hang住一会就可以了
                        if (flag != null && !flag) {
                            return true;
                        }
                    }
                }
                System.out.println("===========日志===========: 工作线程处理请求，商品id=" + request.getProductId());
                // 执行这个request操作
                request.process();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }
}
